﻿using System;
using System.IO;
using System.Runtime.InteropServices;

namespace BepInEx.Unix
{
    internal class UnixStream : Stream
    {
        public UnixStream(int fileDescriptor, FileAccess access)
        {
            Access = access;

            var newFd = UnixStreamHelper.dup(fileDescriptor);
            FileHandle = UnixStreamHelper.fdopen(newFd, access == FileAccess.Write ? "w" : "r");
        }

        public override bool CanRead => Access == FileAccess.Read || Access == FileAccess.ReadWrite;
        public override bool CanSeek => false;
        public override bool CanWrite => Access == FileAccess.Write || Access == FileAccess.ReadWrite;
        public override long Length => throw new InvalidOperationException();

        public override long Position
        {
            get => throw new InvalidOperationException();
            set => throw new InvalidOperationException();
        }


        public FileAccess Access { get; }

        public IntPtr FileHandle { get; }


        public override void Flush() => UnixStreamHelper.fflush(FileHandle);

        public override long Seek(long offset, SeekOrigin origin) => throw new InvalidOperationException();

        public override void SetLength(long value) => throw new InvalidOperationException();

        public override int Read(byte[] buffer, int offset, int count)
        {
            var gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

            var read = UnixStreamHelper.fread(new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64() + offset),
                                              (IntPtr) count, (IntPtr) 1, FileHandle);

            gcHandle.Free();

            return read.ToInt32();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            var gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

            UnixStreamHelper.fwrite(new IntPtr(gcHandle.AddrOfPinnedObject().ToInt64() + offset), (IntPtr) count,
                                    (IntPtr) 1, FileHandle);

            gcHandle.Free();
        }

        private void ReleaseUnmanagedResources() => UnixStreamHelper.fclose(FileHandle);

        protected override void Dispose(bool disposing)
        {
            ReleaseUnmanagedResources();
            base.Dispose(disposing);
        }

        ~UnixStream()
        {
            Dispose(false);
        }
    }
}
